#pragma once

#include "MetaTable.h"

#include <map>
#include <vector>
#include <typeinfo>
#include <functional>


#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
struct name {                                                       \
    typedef char yes[1];                                            \
    typedef char no [2];                                            \
    template <typename U, U> struct type_check;                     \
    template <typename _1> static yes &chk(type_check<Sign, &_1::func> *); \
    template <typename   > static no  &chk(...);                    \
    static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
}

#include "ECS/Component.h"
#include "ECS/Assembly.h"

namespace ECS
{
	TypeID dynamicStringHash(const char* string);

    namespace impl
    {
        template<bool C, typename T = void>
        struct enable_if {
            typedef T type;
        };

        template<typename T>
        struct enable_if<false, T> { };

        HAS_MEM_FUNC(onStart, has_onStart);
        HAS_MEM_FUNC(onDestroy, has_onDestroy);

        template<typename T> 
        typename enable_if<has_onStart<T, void(T::*)()>::value>::type   _callOnStart(T * t) 
        {
            return t->onStart();
        }

        template<typename T> 
        typename enable_if<!has_onStart<T, void(T::*)()>::value>::type   _callOnStart(T * t) 
        {
        }

        template<typename T> 
        typename enable_if<has_onDestroy<T, void(T::*)()>::value>::type   _callOnDestroy(T * t) 
        {
            return t->onDestroy();
        }

        template<typename T> 
        typename enable_if<!has_onDestroy<T, void(T::*)()>::value>::type   _callOnDestroy(T * t) 
        {
        }
    }

    /**
     * Component declaration
     **/

	template <class TComponent>
	bool ECS::Assembly::hasComponent() const
	{
		return getComponent<TComponent>() != nullptr;
	}

    /**
     * CSystem implementation
     **/

    template <class TComponent>
    TComponent* ECS::Assembly::createComponent()
    {
        TComponent* component = new TComponent();

		this->addComponent(component);
        
        return component;
    }
	
    template <class TComponent>
    TComponent* ECS::Assembly::getComponent() const
    {
		TypeID type = getTypeID<TComponent>();

		return (TComponent*)getComponent(type);
    }

	template <typename TArg0>
	void Assembly::sendMessage( TypeID messageID, TArg0 arg0 )
	{
		for (auto it = _components.begin(); it != _components.end(); ++it)
		{
			Component* component = *it;
			mtCALL_EX(component->getMetaTable(), component, messageID, arg0);
		}
	}

	template <class TComponent>
	TComponent* Component::getComponent() const
	{
		return getAssembly()->getComponent<TComponent>();
	}
}

#define ecsSEND_MESSAGE(assembly, message, ...) \
	assembly->sendMessage(ECS::getStaticStringHash(#message), __VA_ARGS__)